#define __SHORT_CIRCUIT_ET__

// Need to short circuit macros for min/max/floor/ceil to enable ET
#include "stencil.hpp"
#include "Wrapper.hpp"
#include "constants.h"

#include "./simple_expression_template/VAR.hpp"
#include "./simple_expression_template/TERM.hpp"
#include "./simple_expression_template/EXPRESSION.hpp"
#include "./simple_expression_template/ADD.hpp"
#include "./simple_expression_template/SUB.hpp"
#include "./simple_expression_template/MIN.hpp"
#include "./simple_expression_template/MAX.hpp"
#include "./simple_expression_template/CEIL.hpp"
#include "./simple_expression_template/FLOOR.hpp"
#include "./simple_expression_template/GE.hpp"
#include "./simple_expression_template/LE.hpp"
#include "./simple_expression_template/FOR.hpp"

#undef __SHORT_CIRCUIT_ET__

#include "TupleHelper.hpp"

template <typename...BOOLS> struct computePredsHelper {
  tuple<BOOLS...> condsE;

  template<int TILETAGDIM, int INTERTASKDIM>
  void computePreds(TileTag<TILETAGDIM, INTERTASKDIM> const & tt) {
    constexpr int NPARAMS = TILETAGDIM - INTERTASKDIM;
    // Tuple holding <dummy, false, ... false>
    condsE = 
      std::tuple_cat(std::make_tuple(false),
                     makeIdenticalTuple<bool, INTERTASKDIM>::make(false));
    
    // Tuple holding <One, t1 ... t_INTERTASKDIM, p1, ... p_NPARAMS>
    auto termsE = 
      std::tuple_cat(make_tuple(ConstantTerm()), 
                     makeCompositeTuple<VariableTerm, INTERTASKDIM+NPARAMS>::make());
    
    auto &t1 = std::get<1>(termsE);
    auto &t2 = std::get<2>(termsE);
    auto &t3 = std::get<3>(termsE);
    auto &pT = std::get<1+INTERTASKDIM>(termsE);
    auto &pM = std::get<2+INTERTASKDIM>(termsE);
    auto &pN = std::get<3+INTERTASKDIM>(termsE);
    auto &pP = std::get<4+INTERTASKDIM>(termsE);
    
    // tuple holding <1, tt[0], ... tt[INTERTASKDIM+NPARAMS-1]>
    auto tagAsTupleE = 
      std::tuple_cat(make_tuple(1), tt.toTuple());

    auto p1 = FOR(t1, ceil(-pN-15,16), floor(pT-3,16));
    auto p2 = 
      FOR(t2, max(t1,-t1-1), 
          min(min(floor(-8*t1+pT-2,8),floor(8*t1+pN+7,8)),floor(pT+pN-2,16)));
    auto p3 = 
      FOR (t3, max(max(0,ceil(t1+t2-1,2)),ceil(16*t2-pN-14,16)), 
           min(min(floor(pT+pN-2,16),floor(16*t2+pN+14,16)),floor(8*t1+8*t2+pN+15,16)));

    auto lbsE = 
      std::make_tuple(ConstantTerm(), 
                      (t1 >= p1.first), (t2 >= p2.first), (t3 >= p3.first));
    auto ubsE = 
      std::make_tuple(ConstantTerm(), 
                      (t1 <= p1.second), (t2 <= p2.second), t3 <= (p3.second));
  
    auto tmp1 = std::get<1>(tagAsTupleE);
    std::get<1>(tagAsTupleE) = tmp1-1;

    std::get<1>(condsE) |= 
      !(std::get<1>(lbsE).Val(tagAsTupleE)) ||
      !(std::get<1>(ubsE).Val(tagAsTupleE));
    std::get<1>(condsE) |= 
      !(std::get<2>(lbsE).Val(tagAsTupleE)) ||
      !(std::get<2>(ubsE).Val(tagAsTupleE));
    std::get<1>(condsE) |= 
      !(std::get<3>(lbsE).Val(tagAsTupleE)) ||
      !(std::get<3>(ubsE).Val(tagAsTupleE));
    assert(std::get<1>(tagAsTupleE) == tmp1-1);
    std::get<1>(tagAsTupleE) = tmp1;
  
    auto tmp2 = std::get<2>(tagAsTupleE);
    std::get<2>(tagAsTupleE) = tmp2-1;
    std::get<2>(condsE) |= 
      !(std::get<2>(lbsE).Val(tagAsTupleE)) ||
      !(std::get<2>(ubsE).Val(tagAsTupleE));
    std::get<2>(condsE) |= 
      !(std::get<3>(lbsE).Val(tagAsTupleE)) ||
      !(std::get<3>(ubsE).Val(tagAsTupleE));
    assert(std::get<2>(tagAsTupleE) == tmp2-1);
    std::get<2>(tagAsTupleE) = tmp2;
    
    auto tmp3 = std::get<3>(tagAsTupleE);
    std::get<3>(tagAsTupleE) = tmp3-1;  
    std::get<3>(condsE) |= 
      !(std::get<3>(lbsE).Val(tagAsTupleE)) ||
      !(std::get<3>(ubsE).Val(tagAsTupleE));
    assert(std::get<3>(tagAsTupleE) == tmp3-1);
    std::get<3>(tagAsTupleE) = tmp3;
  }
};


template<int TILETAGDIM, int INTERTASKDIM>
template<class DC>
void stencil_tuner<TILETAGDIM, INTERTASKDIM>::depends
(const TileTag<TILETAGDIM, INTERTASKDIM> & t, 
 stencil_context<TILETAGDIM, INTERTASKDIM> & c, DC & dc ) const {

  void * wA;
  
  bool dummy = false;
  bool condt1 = false;
  bool condt2 = false;
  bool condt3 = false;
  
  TileTag<TILETAGDIM, INTERTASKDIM> tt(t);

  computePredsHelper<bool, bool, bool, bool> CPE = {};
  CPE.computePreds<TILETAGDIM, INTERTASKDIM> (tt);
  auto condsE = CPE.condsE;  
  std::tie(dummy, condt1, condt2, condt3) = condsE;

  if (!condt1) {
    std::get<0>(tt.m_arr)--;
    dc.depends(c.M, TileTag<TILETAGDIM, INTERTASKDIM> (tt));
    tt.m_arr[0]++;
  } 

  if (!condt2) {
    tt.m_arr[1]--;
    dc.depends(c.M, TileTag<TILETAGDIM, INTERTASKDIM> (tt));
    tt.m_arr[1]++;
  } 

  if (!condt3) {
    tt.m_arr[2]--;
    dc.depends(c.M, TileTag<TILETAGDIM, INTERTASKDIM> (tt));
    tt.m_arr[2]++;
  } 

  return;
}


template void 
stencil_tuner<TILETAGDIMMACRO, INTERTASKDIMMACRO>::depends<CnC::Internal::step_delayer>
(const TileTag<TILETAGDIMMACRO, INTERTASKDIMMACRO> & tt, 
 stencil_context<TILETAGDIMMACRO, INTERTASKDIMMACRO> & c, 
 CnC::Internal::step_delayer &dc ) const;



template <int TILETAGDIM, int INTERTASKDIM>
int64_t stencil_step<TILETAGDIM, INTERTASKDIM>::execute
(const TileTag<TILETAGDIM, INTERTASKDIM> &t, 
 stencil_context<TILETAGDIM, INTERTASKDIM> & c) const
{

  bool dummy = false;
  bool condt1 = false;
  bool condt2 = false;
  bool condt3 = false;

  void *wA;
  
  TileTag<TILETAGDIM, INTERTASKDIM> tt(t);

  computePredsHelper<bool, bool, bool, bool> CPE = {};
  CPE.computePreds<TILETAGDIM, INTERTASKDIM> (tt);
  auto condsE = CPE.condsE;  
  std::tie(dummy, condt1, condt2, condt3) = condsE;

  if (!condt1) {
    std::get<0>(tt.m_arr)--;
    c.M.get(TileTag<TILETAGDIM, INTERTASKDIM> (tt), wA);
    tt.m_arr[0]++;
  } 

  if (!condt2) {
    tt.m_arr[1]--;
    c.M.get(TileTag<TILETAGDIM, INTERTASKDIM> (tt), wA);
    tt.m_arr[1]++;
  } 

  if (!condt3) {
    tt.m_arr[2]--;
    c.M.get(TileTag<TILETAGDIM, INTERTASKDIM> (tt), wA);
    tt.m_arr[2]++;
  } 

  {
    #include "definitions.h"
    int64_t t1 = tt[0];
    int64_t t2 = tt[1];
    int64_t t3 = tt[2];
    int64_t pT = tt[3];
    int64_t pM = tt[4];
    int64_t pN = tt[5];
    int64_t pP = tt[6];

    if (condt1 && condt2 && condt3) {
      c.M.get(TileTag<TILETAGDIM, INTERTASKDIM>(DUMMY_TILE_INDEX, DUMMY_TILE_INDEX, DUMMY_TILE_INDEX, pT,  pN, pN, pN), wA);
    }

    double (*M)[2][Npadded][Npadded][Npadded] = (double (*)[2][Npadded][Npadded][Npadded])(wA);

    int64_t t4, t5, t6, t7, t8, lbv, ubv;
    for (t4=max(max(max(0,ceild(t1+t2-124,125)),ceild(16*t2-pN-998,1000)),ceild(16*t3-pN-998,1000));t4<=min(min(min(min(floord(8*t1+pN+7,500),floord(pT+pN-2,1000)),floord(16*t2+pN+14,1000)),floord(16*t3+pN+14,1000)),floord(8*t1+8*t2+pN+15,1000));t4++) {
      for (t5=max(max(max(max(max(0,8*t1+8*t2),16*t1+1),16*t2-pN),16*t3-pN),1000*t4-pN);t5<=min(min(min(min(min(pT-2,16*t2+14),16*t3+14),1000*t4+998),8*t1+8*t2+15),16*t1+pN+15);t5++) {
        if (t5%2 == 0) {
          for (t6=max(max(16*t2,t5+1),-16*t1+2*t5-15);t6<=min(min(-16*t1+2*t5,16*t2+15),t5+pN);t6++) {
            for (t7=max(16*t3,t5+1);t7<=min(16*t3+15,t5+pN);t7++) {
              lbv=max(1000*t4,t5+1);
              ubv=min(1000*t4+999,t5+pN);
#pragma vector always
#pragma ivdep
              for (t8=lbv;t8<=ubv;t8++) {
                M[0][1][-t5+t6][-t5+t7][-t5+t8]=0.125*(M[0][0][-t5+t6+1][-t5+t7][-t5+t8]-2.0*M[0][0][-t5+t6][-t5+t7][-t5+t8]+M[0][0][-t5+t6-1][-t5+t7][-t5+t8])+0.125*(M[0][0][-t5+t6][-t5+t7+1][-t5+t8]-2.0*M[0][0][-t5+t6][-t5+t7][-t5+t8]+M[0][0][-t5+t6][-t5+t7-1][-t5+t8])+0.125*(M[0][0][-t5+t6][-t5+t7][-t5+t8-1]-2.0*M[0][0][-t5+t6][-t5+t7][-t5+t8]+M[0][0][-t5+t6][-t5+t7][-t5+t8+1])+M[0][0][-t5+t6][-t5+t7][-t5+t8];;
              }
            }
          }
        }else{
          for (t6=max(max(16*t2,t5+1),-16*t1+2*t5-15);t6<=min(min(-16*t1+2*t5,16*t2+15),t5+pN);t6++) {
            for (t7=max(16*t3,t5+1);t7<=min(16*t3+15,t5+pN);t7++) {
              lbv=max(1000*t4,t5+1);
              ubv=min(1000*t4+999,t5+pN);
#pragma ivdep
#pragma vector always
              for (t8=lbv;t8<=ubv;t8++) {
                M[0][0][-t5+t6][-t5+t7][-t5+t8]=0.125*(M[0][1][-t5+t6+1][-t5+t7][-t5+t8]-2.0*M[0][1][-t5+t6][-t5+t7][-t5+t8]+M[0][1][-t5+t6-1][-t5+t7][-t5+t8])+0.125*(M[0][1][-t5+t6][-t5+t7+1][-t5+t8]-2.0*M[0][1][-t5+t6][-t5+t7][-t5+t8]+M[0][1][-t5+t6][-t5+t7-1][-t5+t8])+0.125*(M[0][1][-t5+t6][-t5+t7][-t5+t8-1]-2.0*M[0][1][-t5+t6][-t5+t7][-t5+t8]+M[0][1][-t5+t6][-t5+t7][-t5+t8+1])+M[0][1][-t5+t6][-t5+t7][-t5+t8];;
              }
            }
          }
        }
      }
    }
    c.M.put(TileTag<TILETAGDIM, INTERTASKDIM>(t1,  t2,  t3,  pT,  pM,  pN,  pP), M);  
  }  
  return CnC::CNC_Success;
}

// Template instantiation
template int64_t stencil_step<TILETAGDIMMACRO, INTERTASKDIMMACRO>::execute
(const TileTag<TILETAGDIMMACRO, INTERTASKDIMMACRO> &tt, 
 stencil_context<TILETAGDIMMACRO, INTERTASKDIMMACRO> & c) const;
